import { mimeMap } from './mimeMap'

interface CommonFormData {
  fileManager: any
  data: any
  files: any

  append: (name: string, value: any) => boolean
  appendFile: (name: string, path: any, fileName?: any) => boolean
  getData: () => any
}

class FormData implements CommonFormData {
  fileManager: any
  data: any
  files: any

  constructor() {
    this.fileManager = uni.getFileSystemManager()
    this.data = {}
    this.files = []
  }

  append(name: string, value: any) {
    this.data[name] = value
    return true
  }

  appendFile(name: string, path: any, fileName?: any) {
    const buffer = this.fileManager.readFileSync(path)
    if (!Object.prototype.toString.call(buffer).includes('ArrayBuffer')) {
      return false
    }
    if (!fileName) {
      fileName = getFileNameFromPath(path)
    }
    this.files.push({
      name,
      buffer,
      fileName
    })
    return true
  }

  getData() {
    return convert(this.data, this.files)
  }
}

function getFileNameFromPath(path: any) {
  const idx = path.lastIndexOf('/')
  return path.substr(idx + 1)
}

function convert(data: any, files: any) {
  const boundaryKey = 'unimpFormBoundary' + randString() // 数据分割符，一般是随机的字符串
  const boundary = '--' + boundaryKey
  const endBoundary = boundary + '--'

  let postArray: any = []
  // 拼接参数
  if (data && Object.prototype.toString.call(data) === '[object Object]') {
    for (const key in data) {
      postArray = postArray.concat(formDataArray(boundary, key, data[key]))
    }
  }
  // 拼接文件
  if (files && Object.prototype.toString.call(files) === '[object Array]') {
    for (const i in files) {
      const file = files[i]
      postArray = postArray.concat(
        formDataArray(boundary, file.name, file.buffer, file.fileName)
      )
    }
  }

  // 结尾
  const endBoundaryArray: number[] = []
  endBoundaryArray.push(...toUtf8Bytes(endBoundary))
  postArray = postArray.concat(endBoundaryArray)

  return {
    contentType: 'multipart/form-data; boundary=' + boundaryKey,
    buffer: new Uint8Array(postArray).buffer
  }
}

// 获取随机字符串
function randString() {
  let result = ''
  const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  for (let i = 17; i > 0; --i)
    result += chars[Math.floor(Math.random() * chars.length)]
  return result
}

function formDataArray(
  boundary: any,
  name: string,
  value: any,
  fileName?: string
) {
  let dataString: any = ''
  const isFile = !!fileName

  dataString += boundary + '\r\n'
  dataString += 'Content-Disposition: form-data; name="' + name + '"'
  if (isFile) {
    dataString += '; filename="' + fileName + '"' + '\r\n'
    dataString += 'Content-Type: ' + getFileMime(fileName || '') + '\r\n\r\n'
  } else {
    dataString += '\r\n\r\n'
    dataString += value
  }

  let dataArray: number[] = []
  dataArray.push(...toUtf8Bytes(dataString))

  if (isFile) {
    const fileArray = new Uint8Array(value)
    dataArray = dataArray.concat(Array.prototype.slice.call(fileArray))
  }
  dataArray.push(...toUtf8Bytes('\r'))
  dataArray.push(...toUtf8Bytes('\n'))

  return dataArray
}

function getFileMime(fileName: string) {
  const idx = fileName.lastIndexOf('.')
  const index: string = fileName.substr(idx)
  const mime = mimeMap[index]
  return mime || 'application/octet-stream'
}

function stringToUtf8(string: string) {
  const encoder = new TextEncoder()
  return encoder.encode(string)
}

const toUtf8Bytes = function (str: any) {
  //   const str: any = this
  const bytes: number[] = []
  for (let i = 0; i < str.length; i++) {
    bytes.push(...utf8CodeAt(str, i))
    if (str.codePointAt(i) > 0xffff) {
      i++
    }
  }
  return bytes
}

const utf8CodeAt = function (str: string, i: any): number[] {
  //   const str = this
  const out: number[] = []
  let p = 0
  let c = str.charCodeAt(i)
  if (c < 128) {
    out[p++] = c
  } else if (c < 2048) {
    out[p++] = (c >> 6) | 192
    out[p++] = (c & 63) | 128
  } else if (
    (c & 0xfc00) === 0xd800 &&
    i + 1 < str.length &&
    (str.charCodeAt(i + 1) & 0xfc00) === 0xdc00
  ) {
    // Surrogate Pair
    c = 0x10000 + ((c & 0x03ff) << 10) + (str.charCodeAt(++i) & 0x03ff)
    out[p++] = (c >> 18) | 240
    out[p++] = ((c >> 12) & 63) | 128
    out[p++] = ((c >> 6) & 63) | 128
    out[p++] = (c & 63) | 128
  } else {
    out[p++] = (c >> 12) | 224
    out[p++] = ((c >> 6) & 63) | 128
    out[p++] = (c & 63) | 128
  }
  return out
}

export default FormData
